package com.qn.utils;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @author sxlnsm
 * @date 2022/10/11
 * 工具类是一种工具，可以使任何类中都能使用，并且尽量不与别的类发生耦合
 */
public class JDBCUtils {

    //链接数据库的用户名
    private static String username;
    private static String password;
    private static String url;
    private static String driverClassName;
    private static String databaseName;
    private static String encode;
    private static String timezone;


    //静态代码块初始化数据
    static {
        //读取配置文件中的配置信息
        //1.通过ResourceBundle来读取配合文件，配置文件不需要写文件类型
//        ResourceBundle bundle = ResourceBundle.getBundle("prop");
//        //通过bundle获取配置信息
//        driverClassName = bundle.getString("jdbc.driverClassName");
//        url = bundle.getString("jdbc.url");
//        username = bundle.getString("jdbc.username");
//        password = bundle.getString("jdbc.password");
        //2.通过类加载器获取Resource资源目录的流，该方式需要配置文件的类型
        InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("mysql.properties");
        Properties properties = new Properties();
        //加载配置文件
        try {
            properties.load(inputStream);
            driverClassName = properties.getProperty("jdbc.driverClassName");
            System.out.println(driverClassName);
            url = properties.getProperty("jdbc.url");
            username = properties.getProperty("jdbc.username");
            password = properties.getProperty("jdbc.password");
            databaseName = properties.getProperty("jdbc.databaseName");
            encode = properties.getProperty("jdbc.encode");
            timezone = properties.getProperty("jdbc.timezone");

            //加载驱动
            Class.forName(driverClassName);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //对外提供链接对象的方法
    private static Connection getConnection() throws SQLException {
        //通过驱动管理者类获取链接
        return DriverManager.getConnection(url+databaseName+timezone+encode, username, password);
    }

    //封装增删改的方法
    public static int update(String sql, Object... objects) {
        Connection connection = null;
        //获取链接
        PreparedStatement preparedStatement = null;
        try {
            connection = getConnection();
            //通过链接对象获取预编译对象
            preparedStatement = connection.prepareStatement(sql);
            //给预编译对象添加具体参数
            //遍历可变参数列表
            for (int i = 0; i < objects.length; i++) {
                //获取每一个个参数值
                Object param = objects[i];
                //设置参数到预编译对象中
                preparedStatement.setObject(i + 1, param);
            }
            //执行sql
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            //会在这抛出一个运行时异常
            throw new RuntimeException();
        } finally {
            //关闭资源
            close(null, preparedStatement, connection);
        }
    }

    //封装查询单条的方法
    public static <T> T query(String sql, Class<T> clazz, Object... objects) {
        T t = null;
        Connection connection = null;
        //获取链接
        PreparedStatement preparedStatement = null;
        //声明结果集对象
        ResultSet resultSet = null;
        try {
            connection = getConnection();
            //通过链接对象获取预编译对象
            preparedStatement = connection.prepareStatement(sql);
            //遍历可变参数列表
            for (int i = 0; i < objects.length; i++) {
                //获取每一个个参数值
                Object param = objects[i];
                //设置参数到预编译对象中
                preparedStatement.setObject(i + 1, param);
            }
            //通过预编译对象执行sql
            resultSet = preparedStatement.executeQuery();
            //通过结果集获取字段总数集字段名
            ResultSetMetaData metaData = resultSet.getMetaData();//结果集元数据
            //通过结果集获取每个字段的属性值
            while (resultSet.next()) {
                //创建数据表对应的类的对象
                t = clazz.getConstructor().newInstance();
                int columnCount = metaData.getColumnCount();
                for (int i = 0; i < columnCount; i++) {
                    Object object = resultSet.getObject(i + 1);
                    //通过反射给对象属性赋值
                    Field[] declaredFields = clazz.getDeclaredFields();
                    Field declaredField = declaredFields[i];
                    //设置越过权限校验
                    declaredField.setAccessible(true);
                    //将字段值赋给对象的属性
                    declaredField.set(t, object);
                }
            }
            return t;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        } finally {
            //关闭资源
            close(resultSet, preparedStatement, connection);
        }
    }

    //封装查询多条的方法(通过内省技术给对象的属性赋值)
    public static <T> List<T> queryRowsDescriptor(String sql, Class<T> clazz, Object... objects) {
        //存储多个对象的List集合
        List<T> list = new ArrayList<>();
        T t = null;
        Connection connection = null;
        //获取链接
        PreparedStatement preparedStatement = null;
        //声明结果集对象
        ResultSet resultSet = null;
        try {
            connection = getConnection();
            System.out.println(connection);
            //通过链接对象获取预编译对象
            preparedStatement = connection.prepareStatement(sql);
            //通过预编对象获取参数的元数据
            ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
            //通过参数元数据获取参数的个数
            int parameterCount = parameterMetaData.getParameterCount();
            //循环设置具体参数
            for (int i = 0; i < parameterCount; i++) {
                //给预编译对象设置具体的参数
                preparedStatement.setObject(i+1,objects[i]);
            }
            //通过预编译对象执行sql
            resultSet = preparedStatement.executeQuery();
            //通过结果集获取字段总数集字段名
            ResultSetMetaData metaData = resultSet.getMetaData();//结果集元数据
            //通过结果集获取每个字段的属性值
            while (resultSet.next()) {
                //创建数据表对应的类的对象
                t = clazz.getConstructor().newInstance();
                //通过结果集元数据获取字段个数
                int columnCount = metaData.getColumnCount();
                //循环遍历获取每一个字段
                for (int i = 0; i < columnCount; i++) {
                    //通过结果集元数据对象获取字段名称
                    String columnLabel = metaData.getColumnLabel(i + 1);
                    //通过字段名称和当前数据对应的类的运行时类对象创建属性描述器对象（要求类的属性名和表中的字段名完全相同）
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnLabel,clazz);
                    //通过属性描述器对象获取对象写操作的方法
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    //执行写输入的方法
                    writeMethod.invoke(t,resultSet.getObject(columnLabel));
                }
                //将查询并封装好的对象添加到集合中
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        } finally {
            //关闭资源
            close(resultSet, preparedStatement, connection);
        }
    }


    //封装关闭资源的方法
    private static void close(ResultSet rs, PreparedStatement ps, Connection connection) {
        try {
            if (rs != null) {
                rs.close();
            }
            if (ps != null) {
                ps.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    private JDBCUtils() {
    }


}
